package com.zlx.mall.basic.util.time;

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.Temporal;
import java.util.Date;

/**
 * java.time.* utils
 *
 * @author qingke
 **/
public abstract class DateTimeUtil {

    /**
     * # 时间普及
     *
     * <p>
     * GMT - Greenwich Mean Time - 格林威治时间(可以理解为特殊的时区)
     * 以英国格林威治天文台观测结果得出的时间
     * 这是英国格林威治当地时间, 这个地方的当地时间**过去**被当成世界标准的时间
     * <p>
     * UT - Universal Time - 世界时间
     * 根据原子钟计算出来的时间
     * <p>
     * UTC - Coordinated Universal Time - 协调世界时间
     * 因为地球自转越来越慢, 每年都会比前一年多出零点几秒, 每隔几年协调世界时组织都会给`世界时间(UT)`+1秒
     * 让基于原子钟的`世界时间(UT)`和基于天文学(人类感知)的`格林威治时间(GMT)`相差不至于太大
     * 并将得到的时间称为"UTC", 这是**现在**使用的"协调世界时间"
     * Why is UTC used as the acronym for Coordinated Universal Time instead of CUT?
     * In 1970, the Coordinated Universal Time system was devised by an international advisory \
     * group of technical experts within the International Telecommunication Union (ITU).
     * The ITU felt it was best to designate a single abbreviation for use in all languages in order to minimize confusion.
     * For example, in English the abbreviation for coordinated universal time would be CUT,
     * while in French the abbreviation for "temps universel coordonné" would be TUC.
     * To avoid appearing to favor any particular language,
     * the abbreviation UTC was selected.
     * </p>
     *
     * <p>
     * 协调世界时间(UTC) 不与任何地区位置相关, 也不代表此刻某地的时间
     * 所以当表示一个`当地时间`时, 必须要表明`时区(UTC偏移量)`
     * 当地时间 = UTC + 时区时差(UTC偏移量)
     * e.g. 北京时间(CST) = UTC+8 = UTC+08:00 也会写作 UTC/GMT + 8 或 UTC/Greenwich + 8
     * e.g. 格林威治时间(GMT) = UTC+0(特殊的UTC)
     *
     * <p>
     * ---------------------------------------------------------------------------
     * 重点区分当地时间的`计算来源`和`表示方式`
     * ---------------------------------------------------------------------------
     * 计算来源:
     * 当地时间 = UTC + 时区时差(UTC偏移量)
     * ---------------------------------------------------------------------------
     * 表示方式:
     * ISO-8601格式 +号与此当地时间无任何计算关系
     * 2020-10-13T13:47:02.143+05:30[Asia/Kolkata]
     * 2020-10-13T13:47:02.143 - 当地时间
     * +05:30 - UTC偏移量值/时差
     * [Asia/Kolkata] - 时区名称或UTC偏移量
     * ---------------------------------------------------------------------------
     *
     * <p>
     * # java8时区时间类型
     *
     * <p>
     * ZoneDateTime 时区日期时间
     * 使用方式
     * 时区时间 - ZonedDateTime.now(ZoneId.of("Asia/Kolkata"))
     * 当地时间 - zonedDateTime.toLocalDateTime()
     * - 或者 LocalDateTime.now(ZoneId.of("Asia/Shanghai"))
     * - 或者 LocalDateTime.now(ZoneId.ofHours(8));
     * 包含
     * zoneDateTime.toString()(时区日期时间) - 2020-10-13T11:35:44.416+05:30[Asia/Kolkata]
     * LocalDateTime(当地日期时间) - 2020-10-13T11:35:44.416
     * ZoneOffset(UTC/Greenwich偏移量) - +05:30
     * ZoneId(时区) - Asia/Kolkata
     */


    /**
     * Hours per day.
     */
    public static final int HOURS_PER_DAY = 24;
    /**
     * Minutes per hour.
     */
    public static final int MINUTES_PER_HOUR = 60;
    /**
     * Minutes per day.
     */
    public static final int MINUTES_PER_DAY = MINUTES_PER_HOUR * HOURS_PER_DAY;
    /**
     * Seconds per minute.
     */
    public static final int SECONDS_PER_MINUTE = 60;
    /**
     * Seconds per hour.
     */
    public static final int SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR;
    /**
     * Seconds per day.
     */
    public static final int SECONDS_PER_DAY = SECONDS_PER_HOUR * HOURS_PER_DAY;

    /**
     * Milliseconds per second.
     */
    public static final long MILLIS_PER_SECOND = 1000L;
    /**
     * Milliseconds per minute.
     */
    public static final long MILLIS_PER_MINUTE = MILLIS_PER_SECOND * 60L;
    /**
     * Milliseconds per hour.
     */
    public static final long MILLIS_PER_HOUR = MILLIS_PER_MINUTE * 60L;
    /**
     * Milliseconds per day.
     */
    public static final long MILLIS_PER_DAY = SECONDS_PER_DAY * 1000L;
    /**
     * Microseconds per day.
     */
    public static final long MICROS_PER_DAY = SECONDS_PER_DAY * 1000_000L;
    /**
     * Nanos per second.
     */
    public static final long NANOS_PER_SECOND = 1000_000_000L;
    /**
     * Nanos per minute.
     */
    public static final long NANOS_PER_MINUTE = NANOS_PER_SECOND * SECONDS_PER_MINUTE;
    /**
     * Nanos per hour.
     */
    public static final long NANOS_PER_HOUR = NANOS_PER_MINUTE * MINUTES_PER_HOUR;
    /**
     * Nanos per day.
     */
    public static final long NANOS_PER_DAY = NANOS_PER_HOUR * HOURS_PER_DAY;

    public static final class UTC {

        public static final ZoneId ZONE_ID = ZoneOffset.UTC;

        public static Instant instant() {
            // Instant.now()/Clock.systemUTC()
            return Clock.systemUTC().instant();
        }

        public static LocalTime localTime() {
            return LocalTime.now(Clock.systemUTC());
        }

        public static LocalDate localDate() {
            return LocalDate.now(Clock.systemUTC());
        }

        public static LocalDateTime localDateTime() {
            return LocalDateTime.now(Clock.systemUTC());
        }

        public static OffsetTime offsetTime() {
            return OffsetTime.now(Clock.systemUTC());
        }

        public static OffsetDateTime offsetDateTime() {
            return OffsetDateTime.now(Clock.systemUTC());
        }

        public static ZonedDateTime zonedDateTime() {
            return ZonedDateTime.now(Clock.systemUTC());
        }

        public static MonthDay monthDay() {
            return MonthDay.now(Clock.systemUTC());
        }

        public static YearMonth yearMonth() {
            return YearMonth.now(Clock.systemUTC());
        }

        public static Year year() {
            return Year.now(Clock.systemUTC());
        }

        public static Date toDate(LocalDateTime localDateTime) {
            return Date.from(ZonedDateTime.of(localDateTime, Clock.systemUTC().getZone()).toInstant());
        }

        public static Date toDate(long epochMilli) {
            return Date.from(ZonedDateTime.ofInstant(Instant.ofEpochMilli(epochMilli), Clock.systemUTC().getZone()).toInstant());
        }

        public static LocalDateTime toLocalDateTime(long epochMilli) {
            return LocalDateTime.ofInstant(Instant.ofEpochMilli(epochMilli), Clock.systemUTC().getZone());
        }

        public static LocalDateTime toLocalDateTime(Date date) {
            return LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), Clock.systemUTC().getZone());
        }

        public static ZonedDateTime toZonedDateTime(long epochMilli) {
            return ZonedDateTime.ofInstant(Instant.ofEpochMilli(epochMilli), Clock.systemUTC().getZone());
        }

        public static ZonedDateTime toZonedDateTime(Date date) {
            return ZonedDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), Clock.systemUTC().getZone());
        }

        public static ZonedDateTime toZonedDateTime(LocalDateTime localDateTime) {
            return ZonedDateTime.of(localDateTime, Clock.systemUTC().getZone());
        }
    }

    public static final class System {

        public static final ZoneId ZONE_ID = ZoneId.systemDefault();

        public static Instant instant() {
            return Clock.systemDefaultZone().instant();
        }

        public static LocalTime localTime() {
            return LocalTime.now(Clock.systemDefaultZone());
        }

        public static LocalDate localDate() {
            return LocalDate.now(Clock.systemDefaultZone());
        }

        public static LocalDateTime localDateTime() {
            return LocalDateTime.now(Clock.systemDefaultZone());
        }

        public static OffsetTime offsetTime() {
            return OffsetTime.now(Clock.systemDefaultZone());
        }

        public static OffsetDateTime offsetDateTime() {
            return OffsetDateTime.now(Clock.systemDefaultZone());
        }

        public static ZonedDateTime zonedDateTime() {
            return ZonedDateTime.now(Clock.systemDefaultZone());
        }

        public static MonthDay monthDay() {
            return MonthDay.now(Clock.systemDefaultZone());
        }

        public static YearMonth yearMonth() {
            return YearMonth.now(Clock.systemDefaultZone());
        }

        public static Year year() {
            return Year.now(Clock.systemDefaultZone());
        }

        public static Date toDate(LocalDateTime localDateTime) {
            return Date.from(ZonedDateTime.of(localDateTime, Clock.systemDefaultZone().getZone()).toInstant());
        }

        public static Date toDate(long epochMilli) {
            return Date.from(ZonedDateTime.ofInstant(Instant.ofEpochMilli(epochMilli), Clock.systemDefaultZone().getZone()).toInstant());
        }

        public static LocalDateTime toLocalDateTime(long epochMilli) {
            return LocalDateTime.ofInstant(Instant.ofEpochMilli(epochMilli), Clock.systemDefaultZone().getZone());
        }

        public static LocalDateTime toLocalDateTime(Date date) {
            return LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), Clock.systemDefaultZone().getZone());
        }

        public static ZonedDateTime toZonedDateTime(long epochMilli) {
            return ZonedDateTime.ofInstant(Instant.ofEpochMilli(epochMilli), Clock.systemDefaultZone().getZone());
        }

        public static ZonedDateTime toZonedDateTime(Date date) {
            return ZonedDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), Clock.systemDefaultZone().getZone());
        }

        public static ZonedDateTime toZonedDateTime(LocalDateTime localDateTime) {
            return ZonedDateTime.of(localDateTime, Clock.systemDefaultZone().getZone());
        }
    }

    public static Instant toInstant(long epochMilli) {
        return Instant.ofEpochMilli(epochMilli);
    }

    public static Instant toInstant(Date date) {
        // Date#toInstant();
        return Instant.ofEpochMilli(date.getTime());
    }

    public static Date toDate(long epochMilli) {
        return new Date(epochMilli);
    }

    public static Date toDate(Instant instant) {
        return Date.from(instant);
    }

    public static Date toDate(LocalDateTime localDateTime, ZoneId zone) {
        return Date.from(ZonedDateTime.of(localDateTime, zone).toInstant());
    }

    public static LocalDateTime toLocalDateTime(long epochMilli, ZoneId zoneId) {
        return LocalDateTime.ofInstant(Instant.ofEpochMilli(epochMilli), zoneId);
    }

    public static LocalDateTime toLocalDateTime(Date date, ZoneId zoneId) {
        return LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), zoneId);
    }

    public static ZonedDateTime toZonedDateTime(long epochMilli, ZoneId zoneId) {
        return ZonedDateTime.ofInstant(Instant.ofEpochMilli(epochMilli), zoneId);
    }

    public static ZonedDateTime toZonedDateTime(Date date, ZoneId zoneId) {
        return ZonedDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), zoneId);
    }

    public static ZonedDateTime toZonedDateTime(LocalDateTime localDateTime, ZoneId zoneId) {
        return ZonedDateTime.of(localDateTime, zoneId);
    }

    public static LocalTime parseLocalTime(String text, String pattern) {
        return LocalTime.parse(text, DateTimeFormatter.ofPattern(pattern));
    }

    public static LocalTime parseLocalTime(String text, DateTimeFormatter formatter) {
        return LocalTime.parse(text, formatter);
    }

    public static LocalDate parseLocalDate(String text, String pattern) {
        return LocalDate.parse(text, DateTimeFormatter.ofPattern(pattern));
    }

    public static LocalDate parseLocalDate(String text, DateTimeFormatter formatter) {
        return LocalDate.parse(text, formatter);
    }

    public static LocalDateTime parseLocalDateTime(String text, String pattern) {
        return LocalDateTime.parse(text, DateTimeFormatter.ofPattern(pattern));
    }

    public static LocalDateTime parseLocalDateTime(String text, DateTimeFormatter formatter) {
        return LocalDateTime.parse(text, formatter);
    }

    public static String formatLocalTime(LocalTime localTime, String pattern) {
        return localTime.format(DateTimeFormatter.ofPattern(pattern));
    }

    public static String formatLocalTime(LocalTime localTime, DateTimeFormatter formatter) {
        return localTime.format(formatter);
    }

    public static String formatLocalDate(LocalDate localDate, String pattern) {
        return localDate.format(DateTimeFormatter.ofPattern(pattern));
    }

    public static String formatLocalDate(LocalDate localDate, DateTimeFormatter formatter) {
        return localDate.format(formatter);
    }

    public static String formatLocalDateTime(LocalDateTime dateTime, String pattern) {
        return dateTime.format(DateTimeFormatter.ofPattern(pattern));
    }

    public static String formatLocalDateTime(LocalDateTime dateTime, DateTimeFormatter formatter) {
        return dateTime.format(formatter);
    }

    /**
     * 日期差值
     *
     * @param startDateInclusive e.g. 2010-01-15
     * @param endDateExclusive   e.g. 2011-03-18
     * @return Period
     */
    public static Period between(LocalDate startDateInclusive, LocalDate endDateExclusive) {
        return Period.between(startDateInclusive, endDateExclusive);
    }

    /**
     * 时间差值
     *
     * @param startInclusive startInclusive
     * @param endExclusive   endExclusive
     * @return Duration
     */
    public static Duration between(Temporal startInclusive, Temporal endExclusive) {
        return Duration.between(startInclusive, endExclusive);
    }

}
